using System.Collections.Generic;
using System.Threading;

namespace org.jawk.jrt
{
	/// <summary>
	/// A convenience class that blocks
	/// until any Blockable in the handles set is ready
	/// with data (i.e., will not block).
	/// </summary>
	/// <remarks>
	/// A convenience class that blocks
	/// until any Blockable in the handles set is ready
	/// with data (i.e., will not block).
	/// It's like BlockObject in that this is returned
	/// to the AVM/AwkScript for execution by
	/// the BlockManager.  Unlike the BlockObject, however,
	/// it implements the block() and getNotifierTag()
	/// to manage blocking for a collection of Blockables.
	/// </remarks>
	/// <seealso cref="BlockManager">BlockManager</seealso>
	/// <seealso cref="BlockObject">BlockObject</seealso>
	public sealed class BulkBlockObject : org.jawk.jrt.BlockObject
	{
		/// <summary>
		/// When set true, all empty (AWK-null) handles are treated
		/// as blocked blockables, allowing other blockers to block.
		/// </summary>
		/// <remarks>
		/// When set true, all empty (AWK-null) handles are treated
		/// as blocked blockables, allowing other blockers to block.
		/// </remarks>
		private const bool BYPASS_ALL_BLANK_HANDLES = true;

		private readonly string prefix;

		private readonly System.Collections.Generic.ICollection<string> handles;

		private readonly System.Collections.Generic.IDictionary<string, org.jawk.jrt.IBlockable> blockables;
        private readonly IDictionary<string, ICloseable> closeables; 

		private readonly org.jawk.jrt.IVariableManager vm;

		/// <summary>
		/// Construct a block object which waits for any Blockable
		/// within the set of Blockables to "unblock".
		/// </summary>
		/// <remarks>
		/// Construct a block object which waits for any Blockable
		/// within the set of Blockables to "unblock".
		/// </remarks>
		/// <param name="prefix">First part of the return string for the block operation.</param>
		/// <param name="blockables">All universe of handles and their associated blockables.
		/// 	</param>
		/// <param name="vm">
		/// Required to obtain OFS (used in the construction of the
		/// return string / notifier tag).
		/// </param>
		public BulkBlockObject(string prefix, System.Collections.Generic.IDictionary<string
			, org.jawk.jrt.IBlockable> blockables, org.jawk.jrt.IVariableManager vm)
		{
			if (prefix == null)
			{
				throw new System.ArgumentException("prefix argument cannot be null");
			}
			if (blockables == null)
			{
				throw new System.ArgumentException("blockables argument cannot be null");
			}
			if (vm == null)
			{
				throw new System.ArgumentException("vm argument cannot be null");
			}
			this.prefix = prefix;
			this.handles = new List<string>();
			this.blockables = blockables;
			this.vm = vm;
		}


        /// <summary>
        /// Construct a block object which waits for any Blockable
        /// within the set of Blockables to "unblock".
        /// </summary>
        /// <remarks>
        /// Construct a block object which waits for any Blockable
        /// within the set of Blockables to "unblock".
        /// </remarks>
        /// <param name="prefix">First part of the return string for the block operation.</param>
        /// <param name="blockables">All universe of handles and their associated blockables.
        /// 	</param>
        /// <param name="vm">
        /// Required to obtain OFS (used in the construction of the
        /// return string / notifier tag).
        /// </param>
        public BulkBlockObject(string prefix, System.Collections.Generic.IDictionary<string
            , org.jawk.jrt.ICloseable> closeables, org.jawk.jrt.IVariableManager vm)
        {
            if (prefix == null)
            {
                throw new System.ArgumentException("prefix argument cannot be null");
            }
            if (closeables == null)
            {
                throw new System.ArgumentException("closeables argument cannot be null");
            }
            if (vm == null)
            {
                throw new System.ArgumentException("vm argument cannot be null");
            }
            this.prefix = prefix;
            this.handles = new List<string>();
            this.closeables = closeables;
            this.vm = vm;
        }


		public bool ContainsHandle(string handle)
		{
			return handles.Contains(handle);
		}

		private string block_result = null;

		/// <summary>What to return to the client code when a handle is non-blocking.</summary>
		/// <remarks>
		/// What to return to the client code when a handle is non-blocking.
		/// <p>
		/// The format is as follows :
		/// <blockquote>
		/// <pre>
		/// prefix OFS handle
		/// </pre>
		/// </remarks>
		/// <returns>
		/// The client string containing the handle of the
		/// non-blocking object.
		/// </returns>
		public sealed override string GetNotifierTag()
		{
			//assert prefix != null;
			//assert vm != null;
			return prefix + org.jawk.jrt.JRT.ToAwkString(vm.GetOFS(), vm.GetCONVFMT().ToString
				()) + block_result;
		}

		private static readonly string ALL_HANDLES_ARE_BLANK = "ALL_HANDLES_ARE_BLANK";

		private static readonly string ALL_HANDLES_ARE_BLOCKED = "ALL_HANDLES_ARE_BLOCKED";

		/// <exception cref="System.Exception"></exception>
		public sealed override void Block()
		{
			lock (this)
			{
				string handle = CheckForNonblockHandle();
				if (handle == ALL_HANDLES_ARE_BLANK)
				{
                    Monitor.Wait(this);
					throw new System.Exception("Should never be notified.");
				}
				if (handle == ALL_HANDLES_ARE_BLOCKED)
				{
                    Monitor.Wait(this);
					handle = CheckForNonblockHandle();
				}
				//assert handle != null;
				//assert handle != ALL_HANDLES_ARE_BLOCKED : "handle == ALL_HANDLES_ARE_BLOCKED is an invalid return value ... willBlock() could be of issue";
				block_result = handle;
			}
		}

		private string CheckForNonblockHandle()
		{
			bool all_handles_are_blank = true;
			// cycle through all block_handles
			// check if any of them has accepted sockets
			foreach (string handle in handles)
			{
				if (handle.Equals(string.Empty))
				{
					continue;
				}
				all_handles_are_blank = false;
                org.jawk.jrt.IBlockable blockable = null;
                if (blockables != null)
                {
                    if (blockables.ContainsKey(handle))
                    {
                        blockable = blockables[handle];
                    }
                } else
                {
                    if (closeables.ContainsKey(handle))
                    {
                        blockable = closeables[handle];
                    }
                }
			    if (blockable == null)
				{
					throw new org.jawk.jrt.AwkRuntimeException("handle '" + handle + "' doesn't map to a valid blockable");
				}
				if (!blockable.WillBlock(this))
				{
					return handle;
				}
			}
			//return null;
			if (all_handles_are_blank)
			{
				return ALL_HANDLES_ARE_BLANK;
			}
			else
			{
				return ALL_HANDLES_ARE_BLOCKED;
			}
		}

		private sealed class _IBlockHandleValidator_125 : org.jawk.jrt.IBlockHandleValidator
		{
			public _IBlockHandleValidator_125()
			{
			}

			public string IsBlockHandleValid(string handle)
			{
				// always valid
				return null;
			}
		}

		private static readonly org.jawk.jrt.IBlockHandleValidator no_block_handle_validation
			 = new _IBlockHandleValidator_125();

		public org.jawk.jrt.BlockObject PopulateHandleSet(object[] args, org.jawk.jrt.IVariableManager
			 vm)
		{
			return PopulateHandleSet(args, vm, no_block_handle_validation);
		}

		public org.jawk.jrt.BlockObject PopulateHandleSet(object[] args, org.jawk.jrt.IVariableManager
			 vm, org.jawk.jrt.IBlockHandleValidator validator)
		{
			org.jawk.jrt.BlockObject blocker = this;
			if (args.Length == 0)
			{
				throw new System.ArgumentException(prefix + " blocker requires at least one argument."
					);
			}
			int num_args = args.Length;
			object last_arg = args[num_args - 1];
			if (last_arg is org.jawk.jrt.BlockObject)
			{
				org.jawk.jrt.BlockObject bo = (org.jawk.jrt.BlockObject)last_arg;
				blocker.SetNextBlockObject(bo);
				if (args.Length == 1)
				{
					throw new System.ArgumentException(prefix + " blocker requires at least one item to close-block on."
						);
				}
				--num_args;
			}
			else
			{
				blocker.ClearNextBlockObject();
			}
			// what we know
			// 0 .. num_args-1 = items to close-block
			// num_args >= 1
			System.Collections.Generic.ICollection<string> handle_set = handles;
			handle_set.Clear();
			for (int i = 0; i < num_args; ++i)
			{
				object o = args[i];
				if (o is org.jawk.jrt.AssocArray)
				{
					org.jawk.jrt.AssocArray aa = (org.jawk.jrt.AssocArray)o;
					foreach (object oo in aa.KeySet())
					{
						string handle = org.jawk.jrt.JRT.ToAwkString(oo, vm.GetCONVFMT().ToString());
						string reason = validator.IsBlockHandleValid(handle);
						if (reason != null)
						{
							throw new org.jawk.jrt.AwkRuntimeException(handle + ": invalid handle: " + reason
								);
						}
						// otherwise...
						handle_set.Add(handle);
					}
				}
				else
				{
					string handle = org.jawk.jrt.JRT.ToAwkString(o, vm.GetCONVFMT().ToString());
					string reason = validator.IsBlockHandleValid(handle);
					if (reason != null)
					{
						throw new org.jawk.jrt.AwkRuntimeException(handle + ": invalid handle: " + reason
							);
					}
					// otherwise...
					handle_set.Add(handle);
				}
			}
			return blocker;
		}
	}
}
